package com.linking.mongo.manager;

import cn.hutool.core.util.StrUtil;
import com.borealis.common.utils.StringUtils;
import com.google.common.collect.Lists;
import com.linking.config.pojo.SelfConfigBO.MongoConfigBO.MongoConfig;
import com.linking.mongo.manager.converter.BigDecimalToDecimal128Converter;
import com.linking.mongo.manager.converter.Decimal128ToBigDecimalConverter;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;

/**
 * @Author YaoWeiXin
 * @Date 2019/12/5 15:34
 * @Description mongodb 管理类
 */
@Slf4j
public abstract class AbstractMongoManager implements ApplicationContextAware {

  private final String BEAN_PREFIX = "LinkingMongo-";
  private MongoConfig configPrimary = null;
  private AbstractApplicationContext ctx;

  @Override
  public void setApplicationContext(ApplicationContext ctx) throws BeansException {
    this.ctx = (AbstractApplicationContext) ctx;
  }

  /**
   * 初始mongo连接池
   *
   * @param mongoConfigs mongo配置
   */
  protected void init(List<MongoConfig> mongoConfigs) {
    for (MongoConfig mongoConfig : mongoConfigs) {
      MongoTemplate mongoTemplate = createMongoTemplate(mongoConfig);
      register(mongoConfig.getName(), mongoTemplate);
      if (configPrimary == null) {
        configPrimary = mongoConfig;
      }
    }
  }

  public MongoTemplate retrieveTemplate(String name) {
    String beanName = BEAN_PREFIX + configPrimary.getName();
    return (MongoTemplate) ctx.getBean(beanName);
  }

  /**
   * 注册bean
   *
   * @param name               mongo连接名
   * @param mongoTemplate      mongo操作类
   */
  private void register(String name, MongoTemplate mongoTemplate) {
    String beanName = BEAN_PREFIX + name;
    DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ctx.getBeanFactory();
    // BeanDefinitionBuilder构造过程
    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
        .genericBeanDefinition(mongoTemplate.getClass());
    beanDefinitionBuilder.addConstructorArgValue(mongoTemplate.getMongoDbFactory());
    beanDefinitionBuilder.addConstructorArgValue(mongoTemplate.getConverter());
    if (beanFactory.isBeanNameInUse(beanName)) {
      beanFactory.removeBeanDefinition(beanName);
    }
    beanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
    log.info("register mongoTemplate============{}", beanName);
  }

  private static MongoTemplate createMongoTemplate(MongoConfig mongoConfig) {
    return createMongoTemplate(mongoConfig, null);
  }

  private static MongoTemplate createMongoTemplate(MongoConfig mongoConfig, String db) {
    if (StrUtil.isEmpty(db)) {
      db = mongoConfig.getDb();
    }
    List<ServerAddress> seeds = Lists.newArrayList();
    for (int i = 0; i < mongoConfig.getHosts().size(); i++) {
      seeds.add(new ServerAddress(mongoConfig.getHosts().get(i), mongoConfig.getPorts().get(i)));
    }
    MongoClientOptions.Builder builder = new MongoClientOptions.Builder();
    builder.maxConnectionIdleTime(300000);
    builder.connectionsPerHost(1000);
    builder.socketTimeout(30000);
    MongoClient mongoClient;
    if (StringUtils.checkNotEmpty(mongoConfig.getUser())) {
      mongoClient = new MongoClient(seeds, MongoCredential
          .createCredential(mongoConfig.getUser(), mongoConfig.getAuthDb(),
              mongoConfig.getPwd().toCharArray()), builder.build());
    } else {
      mongoClient = new MongoClient(seeds, builder.build());
    }
    MongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongoClient, db);
    MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(
        new DefaultDbRefResolver(mongoDbFactory), new MongoMappingContext());
    mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
    List<Object> list = new ArrayList<>();
    list.add(new BigDecimalToDecimal128Converter());
    list.add(new Decimal128ToBigDecimalConverter());
    mappingMongoConverter.setCustomConversions(new MongoCustomConversions(list));
    return new MongoTemplate(mongoDbFactory, mappingMongoConverter);
  }

  /**
   * 获取操作类
   *
   * @param name 连接名
   * @return 操作类
   */
  public MongoTemplate getTemplate(String name) {
    String beanName = BEAN_PREFIX + name;
    MongoTemplate template;
    if (ctx.containsBean(beanName)) {
      template = (MongoTemplate) ctx.getBean(beanName);
    } else {
      synchronized (AbstractMongoManager.class) {
        if (ctx.containsBean(beanName)) {
          template = (MongoTemplate) ctx.getBean(beanName);
        } else {
          template = createMongoTemplate(configPrimary, name);
          register(name, template);
        }
      }
    }
    return template;
  }
}
